# -*- coding: utf-8 -*-
"""
 * @file common.py
 * @author SDK_TEAM
 * @brief
 * @version 0.1
 * @date 2022-11-3
 * Copyright:
 * © 2018 北京灵汐科技有限公司 版权所有。
 * 注意：以下内容均为北京灵汐科技有限公司原创，未经本公司允许，不得转载，否则将视为侵权；对于不遵守此声明或者其他违法使用以下内容者，本公司依法保留追究权。
 * © 2018 Lynxi Technologies Co., Ltd. All rights reserved.
 * NOTICE: All information contained here is, and remains the property of Lynxi. This file can not
 * be copied or distributed without the permission of Lynxi Technologies Co., Ltd.
"""
import os
from datetime import datetime
import threading
import time
import cv2
import json
from PIL import Image
import numpy as np
import pylynchipsdk as sdk
import ctypes

sig = [1, -1]
result = 5.96046e-08

def half2float(ib):
    s = (ib >> 15) & 0x1
    e = (ib >> 10) & 0x1f
    m = ib & 0x3ff

    if e == 0:
        return sig[s] * m * result
    else:
        e = 0xff if e == 0x1f else (e - 15 + 127)
        ou = (s << 31) | (e << 23) | (m << 13)
        return np.frombuffer(np.uint32(ou).tobytes(), dtype=np.float32)[0]


def error_check(condition, log=""):
    if condition:
        print("\n****** {} ERROR: {} condition: {}".format(datetime.now(), log, condition))
        os._exit(-1)


###
# function error check and return error info
###
def error_check_ex(condition, code_line, log):
    if condition:
        print(
            "\n****** {}  error_code_line: {}  ERROR: {}".format(
                datetime.now(), code_line, log
            )
        )
        os._exit(-1)


def default_stream_error_handle(stream, errorMsg, params):
    print("******* streamID : ", stream)
    print("******* errorMsg.errCode : ", errorMsg.errCode)
    print("******* errorMsg.errMsg : ", errorMsg.errMsg)
    print("******* errorMsg.errModule : ", errorMsg.errModule)
    print("******* errorMsg.errFunction : ", errorMsg.errFunction)
    print("******* errorMsg.deviceID : ", errorMsg.errDeviceId)
    print("*******   params : ", params)
    os._exit(-1)


def check_device_id(device_id):
    device_num, ret = sdk.lyn_get_device_count()
    if device_id < 0 and device_id >= device_num:
        print(
            "device_id {} invalid, should in range [0, {})".format(
                device_id, device_num
            )
        )
        return False
    else:
        return True


def get_filename_without_ext(path):
    return os.path.splitext(os.path.basename(path))[0]


def print_record():
    global last_second
    global fps
    current_time = datetime.now()
    current_second = current_time.second
    if last_second != current_second:
        current_time = time.strftime("%H:%M:%S", time.localtime())
        print(f"{current_time} fps: {fps}  ")
        fps = 0
        last_second = current_second
    fps += 1


record = threading.local()


def print_frame_rate(channel_name: str, cost_info: map=None) -> None:
    record.fps = getattr(record, 'fps', 0) + 1
    current_second = datetime.now().second
    record.last_second = getattr(record, 'last_second', current_second)
    if record.last_second != current_second:
        info = f'channel {channel_name}, fps {record.fps} '
        if cost_info:
            for k, v in cost_info.items():
                if v[1] > 0:
                    info += f" {k}: {round(v[1]/v[0], 2)}"
                    cost_info[k][0] = 0
                    cost_info[k][1] = 0
                else:
                    print(k, v)
        print(info)
        record.last_second = current_second
        record.fps = 0


def append_text_to_filename(relative_path, text_to_append):
    directory, filename = os.path.split(relative_path)
    filename_without_extension, extension = os.path.splitext(filename)
    new_filename = filename_without_extension + text_to_append + extension
    new_relative_path = os.path.join(directory, new_filename)
    return new_relative_path


def change_file_extension(file_path: str, new_ext: str) -> str:
    file_name, file_extension = os.path.splitext(file_path)
    new_file_path = file_name + new_ext
    return new_file_path

def imgRead(path:str):
    img = cv2.imread(path)
    img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
    return img

def jsonLoad(filename):
    with open(filename,'r') as fobj:
        data = json.load(fobj)
    return data

def cropImage(img, part,rotations, boxes):
    if img.size != (1920, 1080):
        img = img.resize((1920,1080))
    img = img.rotate(rotations[0])
    roi = img.crop(boxes[part])
    return roi

def normalize(img,mean:tuple,std:tuple):
    _,_,c = img.shape
    img = cv2.normalize(img,None,0,1,cv2.NORM_MINMAX)
    img = img.astype(np.float16)
    for i in range(c):
        img[:,:,i] = (img[:,:,i] / mean[i]) - std[i]
    return img

def preprocess(img,rotations,boxes,size:tuple,mean=(0.485, 0.456, 0.406),std=(0.229, 0.224, 0.225)):
    img_list = []
    
    ## mask 不检测的区域
    # image_masked = get_image_masked(img)s

    # 数据归一化操作
    img = Image.fromarray(img)
    ## 切分检测区域
    for part in range(len(boxes)):
        cr = cropImage(img, part, rotations, boxes)
        cr = np.array(cr)
        cr = cv2.resize(src=cr,dsize=size,interpolation=cv2.INTER_AREA)
        cr = normalize(cr,mean,std)
        cr = np.transpose(cr,(2,0,1))    # H W C -> C H W
        cr = np.expand_dims(cr, 0)
        cr = np.ascontiguousarray(cr)
        img_list.append(cr)
    
    return img_list

def is_valid_device(device_id):
    count, ret = sdk.lyn_get_device_count()
    error_check(ret)

    return device_id < count

class ModelInfo:
    def __init__(self, path=None):
        self.model = None
        self.model_desc = None
        self.width = 0
        self.height = 0
        self.batch_size = 0
        self.class_num = 0
        self.input_size = 0
        self.output_size = 0
        if path is not None:
            self.load_model_by_path(path)

    def load_model_by_path(self, path):
        self.model, ret = sdk.lyn_load_model(path)
        error_check(ret)
        self.model_desc, ret = sdk.lyn_model_get_desc(self.model)
        self.width = self.model_desc.inputTensorAttrArray[0].dims[2]
        self.height = self.model_desc.inputTensorAttrArray[0].dims[1]
        if len(self.model_desc.outputTensorAttrArray[0].dims) >= 4:
            self.class_num = self.model_desc.outputTensorAttrArray[0].dims[3] - 5
        self.batch_size = self.model_desc.inputTensorAttrArray[0].batchSize

        self.input_size, ret = sdk.lyn_model_get_input_data_total_len(self.model)
        error_check(ret)
        self.output_size, ret = sdk.lyn_model_get_output_data_total_len(self.model)
        error_check(ret)

    def unload_model(self):
        error_check(sdk.lyn_unload_model(self.model))

SDK_DTYPE = {
    sdk.lyn_data_type_t.DT_INT8: 'int8',
    sdk.lyn_data_type_t.DT_UINT8: 'uint8',
    sdk.lyn_data_type_t.DT_INT32: 'int32',
    sdk.lyn_data_type_t.DT_UINT32: 'uint32',
    sdk.lyn_data_type_t.DT_FLOAT: 'float32',
    sdk.lyn_data_type_t.DT_FLOAT16: 'float16',
    sdk.lyn_data_type_t.DT_UINT16: 'uint16',
}